using System;
using System.Collections.Concurrent;
using PBag = System.Collections.Concurrent.ConcurrentBag<Cz.ICzPoolItem>;

namespace Cz.CzPool
{
    public class CzPool : ICzPool
    {
        private readonly ConcurrentDictionary<Type, (PBag bag, Func<ICzPoolItem> create)>
            pools = new ConcurrentDictionary<Type, (PBag bag, Func<ICzPoolItem> create)>();
        
        public void Start()
        {
            Console.WriteLine("CzPool Start");
        }

        public void InitPool<T>(int initCount, Func<ICzPoolItem> create) where T : ICzPoolItem
        {
            var type = typeof(T);
            var pool = (bag : new PBag(), create : create);

            if (!pools.TryAdd(type, pool))
                throw new Exception("CzPool InitPool Error:" + type.Name);
            
            for (int i = 0; i < initCount; i++)
            {
                pool.bag.Add(create());
            }
        }

        public void ClearPool<T>() where T : ICzPoolItem
        {
            var type = typeof(T);
            if(!pools.TryRemove(type, out var pool))
                throw new Exception("CzPool ClearPool Error 1:" + type.Name);

            while (!pool.bag.IsEmpty)
            {
                if(!pool.bag.TryTake(out var item))
                    throw new Exception("CzPool ClearPool Error 2:" + type.Name);
                item.Release();
            }
        }

        public T Get<T>() where T : ICzPoolItem
        {
            var type = typeof(T);
            if(!pools.TryGetValue(type, out var pool))
                throw new Exception("CzPool Get Error 1:" + type.Name);

            ICzPoolItem item;
            if (!pool.bag.IsEmpty)
            {
                if (!pool.bag.TryTake(out item))
                    throw new Exception("CzPool Get Error 2:" + type.Name);
            }
            else
            {
                item = pool.create();
            }

            return (T)item;
        }

        public void Release<T>(T item) where T : ICzPoolItem
        {
            item.Release();
            
            var type = typeof(T);
            if(!pools.TryGetValue(type, out var pool))
                throw new Exception("CzPool Get Error 1:" + type.Name);
            
            pool.bag.Add(item);
        }
    }
}